/*
 * topological-hex, a program to compute combinatorial hexahedral meshes.
 *
 * Copyright (C) <2018> <Université catholique de Louvain (UCL), Belgique>
 *
 * List of the contributors to the development, description and complete
 * License: see LICENSE file.
 *
 * This program (topological-hex) is free software:
 * you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see COPYING file).  If not,
 * see <http://www.gnu.org/licenses/>.
 */


#include <check.h>
#include <stdlib.h>

#include "queue.h"

#define N 1000

static int data[N];

static
void *producer(void *arg) {
  concurrent_queue *queue = arg;

  for (int i = 0; i < N; i++)
    concurrent_queue_push(queue, &i);
  concurrent_queue_close(queue);
  
  return NULL;
}

static
void *consumer(void *arg) {
  concurrent_queue *queue = arg;

  int value;
  while (concurrent_queue_pop(queue, &value)) {
    ck_assert_int_lt(value, N);
    data[value]++;
  }

  return NULL;
}

START_TEST(test_single_consumer) {
  for (int size = 1; size < N; size += 10) {
    for (int i = 0; i < N; i++)
      data[i] = 0;

    concurrent_queue queue;
    ck_assert(concurrent_queue_init(&queue, size, sizeof(int)) == SUCCESS);

    pthread_t producer_thread, consumer_thread;
    ck_assert(pthread_create(&producer_thread, NULL, producer, &queue) == 0);
    ck_assert(pthread_create(&consumer_thread, NULL, consumer, &queue) == 0);

    ck_assert(pthread_join(producer_thread, NULL) == 0);
    ck_assert(pthread_join(consumer_thread, NULL) == 0);

    for (int i = 0; i < N; i++) {
      ck_assert_msg(data[i] == 1, "Consumer did not update the shared array "
                    "correctly at index %d", i);
    }

    concurrent_queue_release(&queue);
  }
} END_TEST

START_TEST(test_multiple_consumers) {
  for (int size = 1; size < N; size += 10) {
    for (int i = 0; i < N; i++)
      data[i] = 0;

    concurrent_queue queue;
    ck_assert(concurrent_queue_init(&queue, size, sizeof(int)) == SUCCESS);

    pthread_t producer_thread, consumer_threads[4];
    ck_assert(pthread_create(&producer_thread, NULL, producer, &queue) == 0);
    for (size_t i = 0; i < sizeof(consumer_threads)/sizeof(*consumer_threads);
         i++) {
      ck_assert(pthread_create(consumer_threads + i, NULL,
                               consumer, &queue) == 0);
    }

    ck_assert(pthread_join(producer_thread, NULL) == 0);
    for (size_t i = 0; i < sizeof(consumer_threads)/sizeof(*consumer_threads);
         i++) {
      ck_assert(pthread_join(consumer_threads[i], NULL) == 0);
    }

    for (int i = 0; i < N; i++) {
      ck_assert_msg(data[i] == 1, "Consumer did not update the shared array "
                    "correctly at index %d", i);
    }

    concurrent_queue_release(&queue);
  }
} END_TEST

static
void *try_pop_producer(void *arg) {
  concurrent_queue *queue = arg;

  for (int i = 0; i < N; i++)
    concurrent_queue_push(queue, &i);

  return NULL;
}

static
void *try_pop_consumer(void *arg) {
  concurrent_queue *queue = arg;

  int value;
  for (uint32_t i = 0; i < N; i++) {
    while (!concurrent_queue_try_pop(queue, &value));
    ck_assert_int_lt(value, N);
    data[value]++;
  }

  for (uint32_t i = 0; i < 100; i++)
    ck_assert(!concurrent_queue_try_pop(queue, &value));

  return NULL;
}

START_TEST(test_try_pop) {
  for (int size = 1; size < N; size += 10) {
    for (int i = 0; i < N; i++)
      data[i] = 0;

    concurrent_queue queue;
    ck_assert(concurrent_queue_init(&queue, size, sizeof(int)) == SUCCESS);

    pthread_t producer_thread, consumer_thread;
    ck_assert(pthread_create(&producer_thread, NULL,
                             try_pop_producer, &queue) == 0);
    ck_assert(pthread_create(&consumer_thread, NULL,
                             try_pop_consumer, &queue) == 0);

    ck_assert(pthread_join(producer_thread, NULL) == 0);
    ck_assert(pthread_join(consumer_thread, NULL) == 0);

    for (int i = 0; i < N; i++) {
      ck_assert_msg(data[i] == 1, "Consumer did not update the shared array "
                    "correctly at index %d", i);
    }

    concurrent_queue_release(&queue);
  }
} END_TEST

Suite *queue_suite(void) {
  Suite *s = suite_create("Concurrent queue");

  TCase *tcase = tcase_create("Single-producer");
  tcase_add_test(tcase, test_single_consumer);
  tcase_add_test(tcase, test_multiple_consumers);
  tcase_add_test(tcase, test_try_pop);
  suite_add_tcase(s, tcase);

  return s;
}
